using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Komires.MataliPhysics;

namespace Daydream
{
    public class DaydreamGame : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        PhysicsEngine physicsEngine;
        PhysicsScene physicsScene;

        public Dictionary<string, Model> GameMeshes;
        public Dictionary<string, Texture2D> GameTextures;


        public DaydreamGame()
        {
            graphics = new GraphicsDeviceManager(this);
            
            Content.RootDirectory = "Content";

            physicsEngine = new PhysicsEngine("PhysicsEngine");

            GameMeshes = new Dictionary<string, Model>();
            GameTextures = new Dictionary<string, Texture2D>();
        }

        protected override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            physicsScene = physicsEngine.Factory.PhysicsSceneManager.Create("Scene1");


            GameMeshes.Clear();
            GameMeshes.Add("Cube", Content.Load<Model>("Graphics\\3D\\Box"));
            GameMeshes.Add("Cube 2.1", Content.Load<Model>("Graphics\\3D\\Box2"));
            GameMeshes.Add("Cube 2", Content.Load<Model>("Graphics\\3D\\CylinderY"));
            GameTextures.Clear();
            GameTextures.Add("Default", Content.Load<Texture2D>("Graphics\\2D\\Default"));


            // Create a new shape primitive named cubePrimitive
            ShapePrimitive cubePrimitive = physicsScene.Factory.ShapePrimitiveManager.Create("CubePrimitive");
            // Create a cube of edge length 2 (as an atomic geometric object)
            cubePrimitive.CreateBox(1.0f);
            // Create a new shape named cubeShape
            Shape cubeShape = physicsScene.Factory.ShapeManager.Create("CubeShape");
            // Describe cubeShape by single shape primitive (cubePrimitive)
            cubeShape.Set(cubePrimitive, Matrix.Identity, 0.0f);

            // Create a new shape primitive named cubePrimitive
            ShapePrimitive cylinderPrimitive = physicsScene.Factory.ShapePrimitiveManager.Create("CylinderPrimitive");
            // Create a cube of edge length 2 (as an atomic geometric object)
            //cubePrimitive.CreateBox(1.0f);
            cylinderPrimitive.CreateCylinderY(2.0f, 1.0f);
            // Create a new shape named cubeShape
            Shape cylinderShape = physicsScene.Factory.ShapeManager.Create("CylinderShape");
            // Describe cubeShape by single shape primitive (cubePrimitive)
            cylinderShape.Set(cylinderPrimitive, Matrix.Identity, 0.0f);



            //------------------- Cube 1 ------------------------
            // Create a new physics object named Cube 1
            PhysicsObject physicsObject = physicsScene.Factory.PhysicsObjectManager.Create("Cube 1");
            // Determine its shape
            physicsObject.Shape = cylinderShape;
            // Determine its user tag. This information will be used in the camera draw controller.
            physicsObject.UserTagStr = "Cube 2";
            // Determine its position
            physicsObject.InitLocalTransform.SetPosition(27.0f, 56.0f, 20.0f);
            // Determine its scale
            physicsObject.InitLocalTransform.SetScale(5.0f, 10.0f, 5.0f);
            physicsObject.InitLocalTransform.SetRotation(Matrix.CreateRotationX(MathHelper.ToRadians(50.0f)));
            // Determine its density (mass and inertia will be calculated automatically)
            physicsObject.Integral.SetDensity(1.0f);
            // Add physics object to the scene
            physicsScene.UpdateFromInitLocalTransform(physicsObject);

            //------------------- Cube 2 ------------------------
            // Create a new physics object named Cube 2
            physicsObject = physicsScene.Factory.PhysicsObjectManager.Create("Cube 2");
            // Determine its shape
            physicsObject.Shape = cubeShape;
            // Determine its user tag. This information will be used in the camera draw controller.
            physicsObject.UserTagStr = "Cube";
            // Determine its position
            physicsObject.InitLocalTransform.SetPosition(20.0f, 28.0f, 20.0f);
            // Determine its scale
            physicsObject.InitLocalTransform.SetScale(5.0f, 5.0f, 5.0f);
            // Determine its density (mass and inertia will be calculated automatically)
            physicsObject.Integral.SetDensity(1.0f);
            // Add physics object to the scene
            physicsScene.UpdateFromInitLocalTransform(physicsObject);

            //------------------- Quad ------------------------
            // Create a new physics object named Quad
            physicsObject = physicsScene.Factory.PhysicsObjectManager.Create("Quad");
            // Determine its shape
            physicsObject.Shape = cubeShape;
            // Determine its user tag. This information will be used in the camera draw controller.
            physicsObject.UserTagStr = "Cube 2.1";

            // Determine its position
            physicsObject.InitLocalTransform.SetPosition(0.0f, -40.0f, 20.0f);
            // Determine its scale
            physicsObject.InitLocalTransform.SetScale(1000.0f, 31.0f, 1000.0f);
            // Add physics object to the scene
            physicsScene.UpdateFromInitLocalTransform(physicsObject);


            //------------------- Camera ------------------------
            // Create a new physics object named Camera 1
            physicsObject = physicsScene.Factory.PhysicsObjectManager.Create("Camera 1");
            // Determine its position
            physicsObject.InitLocalTransform.SetPosition(0.0f, 30.0f, -122.0f);
            // Determine its orientation
            physicsObject.InitLocalTransform.SetOrientation(Quaternion.Identity);
            // Create the camera and enable the camera controller
            physicsObject.CreateCamera(true);
            // Activate a camera controller
            physicsObject.Camera.Active = true;

            // Update MainWorldTransform. This is necessary if you read the value of main world transform for object.
            physicsObject.UpdateFromInitLocalTransform();

            // Determine camera parameters
            Quaternion quaternionIdentity = new Quaternion(0, 1, 0, 0);
            Matrix rotation = Matrix.CreateRotationY(MathHelper.ToRadians(0));
            Matrix transposeRotation = Matrix.Identity;
            Vector3 position = new Vector3(0, 10, -60);

            //physicsObject.Camera.SetOrientation(ref quaternionIdentity);
            //Matrix.CreateFromQuaternion(ref quaternionIdentity, out rotation);
            //physicsObject.Camera.SetRotation(ref rotation);
            //physicsObject.Camera.Projection.CreatePerspectiveLH(1.0f, 20000.0f, 70.0f, Window.ClientBounds.Width, Window.ClientBounds.Height);
            //physicsObject.MainWorldTransform.GetPosition(ref position);
            //Matrix.Transpose(ref rotation, out transposeRotation);
            //physicsObject.Camera.View.CreateLookAtLH(ref position, ref rotation, 0.0f);
            Matrix proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,
                                                                    graphics.GraphicsDevice.Viewport.AspectRatio,
                                                                    1.0f, 20000.0f);
            
            Matrix view = Matrix.CreateLookAt(new Vector3(0, 20, 130), new Vector3(30, 0, 0), Vector3.Up);

            physicsObject.Camera.View.SetViewMatrix(ref view);
            physicsObject.Camera.Projection.SetProjectionMatrix(ref proj);
            physicsObject.Camera.UpdateFrustum();
            

            // Add camera move controller
            physicsObject.UserControllers.PostTransformMethods += new SimulateMethod(Move);
            // Add camera draw controller
            physicsObject.UserControllers.DrawMethods += new DrawMethod(Draw);

            // Add physics object to the scene
            physicsScene.UpdateFromInitLocalTransform(physicsObject);
        }

        protected override void UnloadContent()
        {
            physicsEngine.Factory.PhysicsSceneManager.RemoveAll();
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            physicsScene.Simulate(gameTime.ElapsedGameTime.TotalSeconds);

            base.Update(gameTime);
        }


        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            


            physicsScene.Draw(gameTime.ElapsedGameTime.TotalSeconds);

            base.Draw(gameTime);
        }


        // Camera move controller
        private void Move(SimulateMethodArgs args)
        {
            // Get physics scene for which the camera was set up
            PhysicsScene physicsScene = physicsEngine.Factory.PhysicsSceneManager.Get(args.OwnerSceneIndex);

            // Get physics object for which the camera was set up
            PhysicsObject physicsObject = physicsScene.Factory.PhysicsObjectManager.Get(args.OwnerIndex);

            

            // Update the lists of visible physics objects from the active camera
            physicsObject.Camera.UpdatePhysicsObjects(true, true);

            // Sort the list of visible physics objects
            physicsObject.Camera.SortDrawPhysicsObjects(PhysicsCameraSortOrderType.DrawPriority);

            // Sort the list of visible transparent physics objects
            physicsObject.Camera.SortTransparentPhysicsObjects(PhysicsCameraSortOrderType.DrawPriority);
        }

        // Declare transformation matrices
        public Matrix World;
        public Matrix View;
        public Matrix Projection;

        // Camera draw controller
        private void Draw(DrawMethodArgs args)
        {
            // Get physics scene for which the camera was set up
            PhysicsScene physicsScene = physicsEngine.Factory.PhysicsSceneManager.Get(args.OwnerSceneIndex);

            // Get physics object for which the camera was set up
            PhysicsObject physicsObject = physicsScene.Factory.PhysicsObjectManager.Get(args.OwnerIndex);

            physicsObject.Camera.View.GetViewMatrix(ref View);
            physicsObject.Camera.Projection.GetProjectionMatrix(ref Projection);
            
            // Code to draw objects visible from the camera
            for (int i = 0; i < physicsObject.Camera.DrawPhysicsObjectCount; i++)
            {
                PhysicsObject drawPhysicsObject = physicsObject.Camera.GetDrawPhysicsObject(i);

                if (drawPhysicsObject.UserControllers.DrawMethods == null)
                {
                    if (String.IsNullOrEmpty(drawPhysicsObject.UserTagStr))
                        continue;

                    drawPhysicsObject.MainWorldTransform.GetTransformMatrix(ref World);

                    Model model = GameMeshes[(string)drawPhysicsObject.UserTagStr];

                    if (model != null)
                    {
                        GraphicsDevice.RasterizerState = RasterizerState.CullClockwise;

                        Matrix worldBone = Matrix.Identity;

                        foreach (ModelMesh modelMesh in model.Meshes)
                        {
                            foreach (BasicEffect effect in modelMesh.Effects)
                            {
                                worldBone = model.Bones[modelMesh.ParentBone.Index].Parent.Transform * World;
                                effect.View = View;
                                effect.World = worldBone;
                                effect.Projection = Projection;
                                effect.LightingEnabled = false;

                                if (!String.IsNullOrEmpty(drawPhysicsObject.Material.UserTagStr))
                                {
                                    effect.TextureEnabled = true;
                                    effect.Texture = GameTextures[(string)drawPhysicsObject.Material.UserTagStr];
                                }
                                //else
                                //{
                                //    effect.TextureEnabled = true;
                                //    effect.Texture = GameTextures["Default"];
                                //}
                            }
                            this.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
                            modelMesh.Draw();
                        }
                    }
                }
            }
        }
    }
}
